/*
 *  sniff232 - Simple RS232 sniffer
 *  
 *  Trivial program to sniff communications made between two RS232 devices
 *
 *  Computer <------> [port1] Computer with sniff232 [port2] <------> Device
 *
 *  Use at your own risk.
 *
 *  Cicuttin Matteo (C) 2007-2010 - matteo.cicuttin@gmail.com
 *  Released under BSD license.
 *
 *  To compile:
 *  gcc -O2 -Wall -pedantic -o sniff232 sniff232.c
 *
 *  History:
 *  v0.0 (11/07/2007) -		Created sniff232
 *  v0.1 (06/03/2007) -		Added some functionalities
 *	v0.2 (14/03/2007) -		Added some functionalities
 *
 */
 
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/select.h>
#include <signal.h>
#include <err.h>
#include <sysexits.h>

#include "sniff232.h"
#include "hexdump.h"
#include "serial.h"
#include "sniffer.h"
#include "nc.h"
#include "tcpbr.h"

int ctrlc;

static void    ctrlc_handler(int);
static void    usage(char *);

static void
ctrlc_handler(int unused)
{
    printf("\rDetected CTRL-C, exiting.\n");
    ctrlc = 1;
}

static void
usage(char *progname)
{
    printf("sniff232 v0.1 - (C) Matteo Cicuttin 2007-2010\n\n");
    printf("Usage: %s -P <port1> -p <port2> -S <port1 speed>\n\n", progname);
		   
    printf("Mandatory options:\n");       
    printf("\t-P <port1>: First serial port device\n");
    printf("\t-p <port2>: Second serial port device\n");
    printf("\t-S <port1 speed>: Speed of first serial port\n\n");
	printf("Other options:\n");
    printf("\t-s <port2 speed>: Speed of second serial port. This is not needed if\n"
           "\t                  you want the same speed on both ports\n");
	printf("\t-N <name>: Give an alternative name to port1\n");
    printf("\t-n <name>: Give an alternative name to port2\n");
    printf("\t-f <filename>: Use file <filename> instead of stdout to print data dump\n");
	printf("\t-b Enable output buffering\n");
}


static int
run(config_t *cfg)
{
	/* Each mode flag increments cfg->modes by 1, so if multiple modes are
	 * specified we easily detect the error. But this is a bad solution, I
	 * should do something better
	 */
	if ( cfg->modes > 1 )
		errx(EX_USAGE, "-r, -w and -t are mutually exclusive");
		
	switch( RUN_MODE(cfg->flags) )
	{
		case NO_MODE:
			prepare_sniffer_mode(cfg);
			run_sniffer(cfg);
			break;
			
		case NC_MODE_READ:
			prepare_nc_read_mode(cfg);
			break;
			
		case NC_MODE_WRITE:
			prepare_nc_write_mode(cfg);
			break;
			
		case TCPBR_MODE:
			prepare_tcpbr_mode(cfg);
			break;
	}
	
    return 0;
}

inline int
xfer(int src_fd, int dst_fd, char *buf, int bufsz)
{
    int rd;
    rd = read(src_fd, buf, bufsz);
	if (rd == -1)
        return -1;

	if ( write(dst_fd, buf, rd) == -1 )
        return -1;
    
    return rd;
}

void
cleanup(config_t *cfg)
{
    close(cfg->fd1);
    close(cfg->fd2);
	if (cfg->dump_filename)
		fclose(cfg->dump_stream);
}

int
main(int argc, char **argv)
{	
	int ch;
	struct sigaction sa;
	config_t cfg;
	
	bzero(&cfg, sizeof(config_t));
	cfg.progname = argv[0];
	
	while ( (ch = getopt(argc, argv, "P:p:S:s:N:n:f:hbrw")) != -1)
	{
	    switch (ch)
	    {
	        case 'P':
	            cfg.P_arg = optarg;
				if (cfg.N_arg == NULL)
					cfg.N_arg = optarg;
	            break;
	        
	        case 'p':
	            cfg.p_arg = optarg;
				if (cfg.n_arg == NULL)
					cfg.n_arg = optarg;
	            break;
	            
	        case 'S':
	            cfg.port1_spd = atoi(optarg);
	            if (cfg.port2_spd == 0)
	                cfg.port2_spd = cfg.port1_spd;
	            break;
	            
	        case 's':
	            cfg.port2_spd = atoi(optarg);
	            break;
				
			case 'N':
				cfg.N_arg = optarg;
				break;
				
			case 'n':
				cfg.n_arg = optarg;
				break;
	            
	        case 'f':
	            cfg.dump_filename = optarg;
	            break;
	        
	        case 'h':
	            usage(argv[0]);
	            exit(0);
	            break;
			
			case 'b':
				cfg.flags |= OBUF_ENABLE;
				break;
				
			case 'r':
				cfg.flags |= NC_MODE_READ;
				break;
				
			case 'w':
				cfg.flags |= NC_MODE_WRITE;
				break;
				
	        case '?':
	        default:
	            errx(EX_USAGE, "Bad arguments on command line, try running with -h");
	            break;
	    }
	}
	
	ctrlc = 0;
	sa.sa_handler = ctrlc_handler;
	sa.sa_flags = 0;
	
	if (sigaction(SIGINT, &sa, NULL) < 0)
    {
        cleanup(&cfg);
        errx(EX_OSERR, "%s", strerror(errno));
    }
	
	run(&cfg);
	cleanup(&cfg);
	
	return 0;
}

